# ClickUl Development Documentation

> GTA5 Enhanced Edition (E&E) Lua Menu Framework
> Modular Menu System Based on LEXIS Script API

**Last Updated**: 2025-12-26
**Lua Version**: Lua 5.4
**Target Platform**: LEXIS (GTA5 E&E)

---

## 📋 Table of Contents

1. [Project Overview](#project-overview)
2. [Quick Start](#quick-start)
3. [Architecture Design](#architecture-design)
4. [Directory Structure](#directory-structure)
5. [Core Modules](#core-modules)
6. [Development Guide](#development-guide)
7. [Code Standards](#code-standards)
8. [Debugging Methods](#debugging-methods)
9. [Performance Optimization](#performance-optimization)
10. [FAQ](#faq)

---

## Project Overview

### Tech Stack

- **Language**: Lua 5.4
- **API**: LEXIS Script API
- **UI**: Custom Rendering Engine (Based on LEXIS GUI)
- **Configuration**: JSON
- **Internationalization**: JSON-based Translation System

### Core Features

- ✅ **Modular Architecture**: Features developed and maintained independently
- ✅ **High-Performance Rendering**: Multi-level caching system, optimized text and graphics rendering
- ✅ **Theme System**: Support for multiple themes and customization
- ✅ **Internationalization**: Multi-language support with runtime switching
- ✅ **Configuration Persistence**: Automatic saving of user settings
- ✅ **Debug System**: Comprehensive logging and performance monitoring

---

## Quick Start

### Requirements

1. GTA5 Enhanced Edition (E&E)
2. LEXIS Script Loader
3. Text Editor (Recommended: VS Code + Lua Language Server)

### Project Structure

```
ClickUl/
├── UI/                  # UI Core System
├── Features/            # Feature Modules
├── Config/              # Configuration System
├── Data/                # Data Definitions
├── Utils/               # Utility Libraries
├── Lib/                 # Third-party Libraries
├── languages/           # Translation Files
└── Themes/              # Theme Files
```

### Creating a New Feature Module

```lua
-- Features/MyFeature.lua
local module = {}

-- [Dependencies]
local ui = nil
local config = nil
local natives = nil
local debugger = nil

-- [State]
local state = {
    enabled = false,
    value = 0
}

-- [Init]
function module.init(ui_lib, cfg_lib, nat_lib, _, _, _, debug_lib)
    ui = ui_lib
    config = cfg_lib
    natives = nat_lib
    debugger = debug_lib

    if debugger then
        debugger.info("[MyFeature] Module initialized")
    end

    return true
end

-- [Draw UI]
function module.draw()
    ui.Group("My Feature", function()
        state.enabled = ui.Checkbox("Enable", state.enabled)

        if state.enabled then
            state.value = ui.Slider("Value", state.value, 0, 100, 1)
        end
    end)
end

-- [Background Loop]
function module.on_tick()
    if state.enabled then
        -- Execute per-frame logic here
    end
end

return module
```

---

## Architecture Design

### System Layers

```
┌─────────────────────────────────────┐
│        Features Modules             │  ← Feature Layer
│  (Heist, Vehicle, Weapon, etc.)    │
├─────────────────────────────────────┤
│         UI Framework                │  ← UI Layer
│  (Widgets, Layout, Notification)    │
├─────────────────────────────────────┤
│      Renderer & State               │  ← Rendering Layer
│  (Drawing, Caching, Input)          │
├─────────────────────────────────────┤
│      Config & Persistence           │  ← Persistence Layer
│  (Theme, Language, Features)        │
├─────────────────────────────────────┤
│         LEXIS API                   │  ← API Layer
│  (gui, natives, file, account)      │
└─────────────────────────────────────┘
```

### Data Flow

```
User Input → Input System → State Update → Renderer → Screen
                ↓                ↓
           UI Widgets      Config Save
```

### Module Dependencies

```
Features → UI Framework → Renderer → LEXIS API
    ↓            ↓            ↓
  Config   ←   Theme    ←  State
    ↓
  JSON File
```

---

## Directory Structure

### UI/ - UI Core System

```
UI/
├── core.lua          # UI System Coordinator, Module Initialization
├── state.lua         # Global State Management
├── input.lua         # Input Handling (Mouse, Keyboard)
├── renderer.lua      # Rendering Engine (Text, Graphics, Caching)
├── theme.lua         # Theme Configuration Loading and Management
├── layout.lua        # Layout System (Windows, Scrolling, Positioning)
├── widgets.lua       # UI Controls (Buttons, Sliders, Checkboxes, etc.)
└── notification.lua  # Notification System
```

### Features/ - Feature Modules

```
Features/
├── Self.lua          # Player Self Features
├── Player.lua        # Player Management
├── Online.lua        # Online Features
├── Weapon.lua        # Weapon Features
├── Spawn.lua         # Spawn Features
├── Vehicle.lua       # Vehicle Features
├── World.lua         # World Features
├── Game.lua          # Game Settings (Time, Weather)
├── Heist.lua         # Heist Missions
├── Setting.lua       # Settings Interface
└── PlayerInfo.lua    # Player Info Overlay
```

### Config/ - Configuration System

```
Config/
├── features.lua      # Feature Configuration Persistence
└── Themes/           # Theme JSON Files
```

### Data/ - Data Definitions

```
Data/
├── heist.lua         # Heist Mission Data (Addresses, Constants, Options)
├── vehicle.lua       # Vehicle Data
└── weapon.lua        # Weapon Data
```

### Utils/ - Utility Libraries

```
Utils/
└── HeistHelper.lua   # Heist Mission Helper Tools
```

### Lib/ - Third-party Libraries

```
Lib/
├── json.lua          # JSON Parser
└── lang.lua          # Internationalization System
```

---

## Core Modules

### 1. Renderer (UI/renderer.lua)

**Responsibility**: Low-level implementation of all graphics drawing

**Core Features**:
- Text rendering (with caching)
- Rectangle drawing (filled, outlined, rounded)
- Image loading and drawing
- Multi-level caching system (vec2, color, text, label)

**Performance Optimizations**:
```lua
-- Vec2 Cache: Avoid repeated creation of coordinate objects
local VEC2_CACHE_LIMIT = 4096

-- Text Object Cache: Avoid repeated creation of gui.text() objects
local TEXT_CACHE_LIMIT = 1500

-- Label Cleanup Cache: Remove control ID suffixes
local CLEAN_LABEL_MAX = 2048
```

**Key APIs**:
```lua
renderer.draw_rect(x, y, w, h, color_obj, rounding, flags)
renderer.draw_text_ex(text, x, y, size, color_obj, align, wrap, cache_id)
renderer.load_image(path)
renderer.s(value)  -- Scale conversion
```

### 2. Widgets (UI/widgets.lua)

**Responsibility**: Implementation of all UI controls

**Control List**:
- `Button(label)` - Button
- `Checkbox(label, state)` - Checkbox
- `Toggle(label, state)` - Toggle Switch
- `Slider(label, value, min, max, step)` - Slider
- `Combo(label, index, items)` - Dropdown
- `TextInput(label, value, max_length)` - Text Input
- `ColorPicker(label, table, key)` - Color Picker
- `Separator(text)` - Separator
- `ProgressBar(progress, min, max)` - Progress Bar

**Control State Management**:
```lua
-- Each control maintains its own state (hover, active, clicked)
local function get_state_key(label)
    return clean_label(label)
end

state.hover[key] = is_hovering
state.active[key] = is_clicking
```

#### 2.1. UI Component Best Practices

**✅ Recommended Usage**

##### Basic Control Usage

```lua
-- Button: Simple button
if ui.Button(_T("Click Me")) then
    -- Execute when button is clicked
    perform_action()
end

-- Checkbox: Checkbox (with state management)
state.enabled = ui.Checkbox(_T("Enable Feature"), state.enabled)

-- Toggle: Toggle switch (different visual style)
state.auto_mode = ui.Toggle(_T("Auto Mode"), state.auto_mode)

-- Slider: Slider (with range and step)
state.value = ui.Slider(_T("Value"), state.value, 0, 100, 1)
--                       Label        Current    Min  Max Step
```

##### Dropdowns and Input

```lua
-- Combo: Dropdown selection
local items = {"Option 1", "Option 2", "Option 3"}
state.selected_idx = ui.Combo(_T("Select Option"), state.selected_idx, items)

-- Get selected value
local selected_value = items[state.selected_idx]

-- TextInput: Text input field
state.player_name = ui.TextInput(_T("Player Name"), state.player_name, 32)
--                                 Label            Current Value    Max Length
```

##### Color Picker

```lua
-- ColorPicker: Directly modifies config table
local colors = {
    primary = {r=255, g=100, b=50, a=255}
}

-- Will directly modify colors.primary
ui.ColorPicker(_T("Primary Color"), colors, "primary")

-- Access color values
local r = colors.primary.r
local g = colors.primary.g
```

##### Progress Bar and Separator

```lua
-- ProgressBar: Display progress
local current = 350
local max_value = 500
ui.ProgressBar(current, 0, max_value)

-- Separator: Divider line (with optional text)
ui.Separator(_T("Advanced Options"))
ui.Separator()  -- No text separator
```

**❌ Common Mistakes**

```lua
-- ❌ Error 1: Forgetting to use return value
ui.Checkbox(_T("Enable"), state.enabled)  -- State won't update!

-- ✅ Correct: Receive return value
state.enabled = ui.Checkbox(_T("Enable"), state.enabled)

-- ❌ Error 2: Using same label in loops
for i = 1, 3 do
    ui.Button("Item")  -- Only first will respond!
end

-- ✅ Correct: Use unique labels (manual addition required in loops)
for i = 1, 3 do
    ui.Button("Item##" .. i)  -- Content after ## not displayed but used for differentiation
end

-- ❌ Error 3: Forgetting translation
ui.Button("Click Me")  -- No multi-language support

-- ✅ Correct: Use translation
ui.Button(_T("Click Me"))
```

**💡 Automatic Unique ID Mechanism**

ClickUl has implemented automatic unique ID mechanism based on Group context:

```lua
-- ✅ Same labels in different Groups are automatically differentiated (Combo/TextInput)
ui.Group("Player Settings", function()
    state.mode = ui.Combo("Mode", state.mode, {"Walk", "Run"})
    -- Internal ID: "Player Settings/Mode"
end)

ui.Group("Vehicle Settings", function()
    state.mode = ui.Combo("Mode", state.mode, {"Normal", "Turbo"})
    -- Internal ID: "Vehicle Settings/Mode" ✅ No conflict!
end)

-- ⚠️ Loops still require manual unique identifiers
ui.Group("Items", function()
    for i = 1, 3 do
        -- ❌ Error: All button IDs are "Items/Item"
        ui.Button("Item")
    end

    for i = 1, 3 do
        -- ✅ Correct: IDs are "Items/Item##1", "Items/Item##2", ...
        ui.Button("Item##" .. i)
    end
end)
```

**Controls supporting automatic unique ID**:
- ✅ **Combo** - Dropdown
- ✅ **TextInput** - Text input
- ✅ **Checkbox** - Checkbox (animation state only)
- ❌ **Button/Slider** - Stateless, not needed

**Scenarios still requiring manual `##` addition**:
1. Controls generated in loops
2. Potentially duplicate labels in conditional rendering
3. Controls outside Groups that may conflict

**🎯 Advanced Techniques**

##### Conditional Rendering

```lua
-- Display different controls based on state
if state.mode == "basic" then
    state.value = ui.Slider(_T("Simple Value"), state.value, 0, 100, 1)
else
    state.value = ui.Slider(_T("Advanced Value"), state.value, 0, 1000, 10)
    state.multiplier = ui.Slider(_T("Multiplier"), state.multiplier, 1, 10, 0.1)
end
```

##### Group Organization

```lua
-- Use Group to organize related controls
ui.Group(_T("Player Settings"), function()
    state.god_mode = ui.Checkbox(_T("God Mode"), state.god_mode)
    state.invisible = ui.Checkbox(_T("Invisible"), state.invisible)
    state.speed = ui.Slider(_T("Speed Multiplier"), state.speed, 1, 10, 0.5)
end)

ui.Group(_T("Vehicle Settings"), function()
    state.auto_repair = ui.Checkbox(_T("Auto Repair"), state.auto_repair)
    state.boost = ui.Slider(_T("Boost Power"), state.boost, 1, 5, 0.1)
end)

-- 💡 Automatic Unique ID: Same labels won't conflict even in different contexts
ui.Group(_T("Player Mode"), function()
    state.player_mode = ui.Combo(_T("Mode"), state.player_mode, {"Normal", "Aggressive", "Defensive"})
    state.player_speed = ui.TextInput(_T("Speed"), state.player_speed, 10)
end)

ui.Group(_T("Vehicle Mode"), function()
    -- ✅ Same labels "Mode" and "Speed", but automatic unique IDs in different Groups
    state.vehicle_mode = ui.Combo(_T("Mode"), state.vehicle_mode, {"Eco", "Sport", "Off-road"})
    state.vehicle_speed = ui.TextInput(_T("Speed"), state.vehicle_speed, 10)
    -- Internal IDs: "Vehicle Mode/Mode" and "Vehicle Mode/Speed" ✅ No conflict!
end)
```

##### Inline Layout

```lua
-- Use Row for horizontal arrangement
ui.Row({0.5, 0.5}, function(col)
    if col == 1 then
        if ui.Button(_T("Apply")) then
            apply_settings()
        end
    else
        if ui.Button(_T("Reset")) then
            reset_settings()
        end
    end
end)
```

##### Dynamic Lists

```lua
-- Render dynamic lists
local players = get_player_list()

for i, player in ipairs(players) do
    ui.Row({0.7, 0.3}, function(col)
        if col == 1 then
            ui.Button(player.name .. "##player_" .. i)
        else
            if ui.Button(_T("Kick") .. "##kick_" .. i) then
                kick_player(player.id)
            end
        end
    end)
end
```

##### Disabled Control Effect

```lua
-- Control availability through conditions
if not state.enabled then
    -- Display disabled hint
    ui.Button(_T("Feature Disabled"))
else
    -- Normal control
    if ui.Button(_T("Execute")) then
        perform_action()
    end
end
```

**⚠️ Performance Considerations**

```lua
-- ✅ Recommended: Cache expensive calculations
local cached_items = nil

local function get_items()
    if not cached_items then
        cached_items = expensive_calculation()
    end
    return cached_items
end

function module.draw()
    local items = get_items()
    state.selected = ui.Combo(_T("Select"), state.selected, items)
end

-- ❌ Not recommended: Recalculate every frame
function module.draw()
    local items = expensive_calculation()  -- Executes every frame!
    state.selected = ui.Combo(_T("Select"), state.selected, items)
end
```

### 3. Layout (UI/layout.lua)

**Responsibility**: Window layout and scroll management

**Core Components**:
- `BeginWindow()` / `EndWindow()` - Window container
- `BeginGroup()` / `EndGroup()` - Group container (with scrolling)
- `DualColumn(left_fn, right_fn)` - Dual column layout
- Scroll system (mouse wheel, touchpad, scrollbar)

**Scroll Implementation**:
```lua
-- Virtual scrolling: Only render visible area
local visible_start = scroll_y
local visible_end = scroll_y + visible_height
local content_height = calculate_content_height()

-- Smooth scroll animation
target_scroll = clamp(target_scroll, 0, max_scroll)
current_scroll = lerp(current_scroll, target_scroll, 0.3)
```

### 4. Theme (UI/theme.lua)

**Responsibility**: Theme configuration loading and management

**Configuration Structure**:
```lua
theme.data = {
    language = "en",
    bg_image_enabled = false,
    bg_image_file = "",
    bg_image_opacity = 255,
    sidebar_icons = {},
    notification_icons = {},
    notification_position = "bottom_right",

    base = { -- Size parameters
        w = 700, h = 600,
        padding = 20,
        item_h = 40,
        -- ... more parameters
    },

    colors = { -- Color definitions
        ["Window Background"] = {r=20, g=20, b=25, a=240},
        ["Text Primary"] = {r=255, g=255, b=255, a=255},
        -- ... more colors
    }
}
```

**APIs**:
```lua
theme.reset()                    -- Reset to default theme
theme.load(theme_name)          -- Load theme
theme.save_as(theme_name)       -- Save current as new theme
theme.list_themes()             -- List all themes
```

### 5. Language (Lib/lang.lua)

**Responsibility**: Internationalization and translation management

**Translation Format** (JSON):
```json
{
  "English Key": "Translated Text",
  "Player": "Player",
  "Settings": "Settings"
}
```

**Usage**:
```lua
local lang = require("ClickUl.Lib.lang")

-- Use translation in modules
local function _T(text)
    if lang then return lang.get(text) end
    return text
end

ui.Button(_T("Save"))  -- Display translated text
```

**APIs**:
```lua
lang.init(features_config, _, debugger)
lang.load(lang_code)             -- Load language (e.g., "zh-cn")
lang.reload()                    -- Reload current language
lang.current()                   -- Get current language code
lang.get(key)                    -- Get translated text
lang.get_available_languages()   -- Get available language list
```

### 6. Config (Config/features.lua)

**Responsibility**: Feature module configuration persistence

**Configuration Structure**:
```lua
{
    language = "zh-cn",
    debug = { enabled = false, log_level = 2, ... },
    modules = {
        Game = {
            freeze_time = false,
            custom_hour = 12,
            weather_idx = 1
        },
        Vehicle = { ... }
    }
}
```

**APIs**:
```lua
features_config.init(_, debugger)
features_config.set_enabled(enabled)    -- Enable/disable auto-save
features_config.force_save()            -- Force save
features_config.set_module(name, data)  -- Set module configuration
features_config.get_module(name)        -- Get module configuration
features_config.set_language(code)      -- Set language
features_config.get_language()          -- Get language
```

### 7. Notification (UI/notification.lua)

**Responsibility**: In-game notification system supporting multiple notification types and custom icons

#### Notification Types

ClickUl supports the following notification types, each with different colors and icons:

| Type | Purpose | Color |
|------|---------|-------|
| **Success** | Successful operation | Green |
| **Warning** | Warning message | Orange |
| **Error** | Error message | Red |
| **Info** | General information | Blue |
| **Join** | Player joined | Custom |
| **Leave** | Player left | Custom |
| **Spectator** | Spectator detected | Custom |
| **Modder** / **Modder Detection** | Cheat detection | Custom |
| **Script Event** | Script event | Custom |
| **Network Event** | Network event | Custom |
| **Protection** | Protection triggered | Custom |
| **Player Manager** | Player management | Custom |

#### Basic Usage

**✅ Recommended Method 1: Using UI Module**

```lua
-- Use ui.notify in modules
function module.draw()
    if ui.Button(_T("Save")) then
        save_config()
        ui.notify(_T("Settings saved successfully"), "Success")
    end
end
```

**✅ Recommended Method 2: Using Helper Function**

```lua
-- Create notification helper function
local function show_toast(text, type_str)
    if ui and ui.notify then
        ui.notify(text, type_str or "Info")
    elseif util and util.toast then
        util.toast(text)  -- Fallback to Lexis native notification
    end
end

-- Usage
show_toast(_T("Player teleported"), "Success")
show_toast(_T("Invalid input"), "Error")
show_toast(_T("Processing..."), "Info")
```

#### Advanced Usage

##### Notification Examples for Different Scenarios

```lua
-- Successful operation
function apply_vehicle_mod()
    if natives.SET_VEHICLE_MOD(...) then
        ui.notify(_T("Vehicle modified successfully"), "Success")
    end
end

-- Error handling
function teleport_player(x, y, z)
    if not is_valid_position(x, y, z) then
        ui.notify(_T("Invalid teleport coordinates"), "Error")
        return false
    end

    -- Execute teleport
    ui.notify(_T("Teleported to location"), "Success")
    return true
end

-- Warning message
function enable_godmode()
    if not is_in_game() then
        ui.notify(_T("Warning: Not in game session"), "Warning")
        return
    end

    state.godmode = true
    ui.notify(_T("God mode enabled"), "Info")
end

-- Player events
function on_player_join(player_name)
    ui.notify(player_name .. _T(" joined the session"), "Join")
end

function on_player_leave(player_name)
    ui.notify(player_name .. _T(" left the session"), "Leave")
end

-- Protection events
function on_script_event_blocked(event_name)
    ui.notify(_T("Blocked script event: ") .. event_name, "Script Event")
end

function on_modder_detected(player_name)
    ui.notify(_T("Modder detected: ") .. player_name, "Modder Detection")
end
```

##### Conditional Notifications

```lua
-- Only display notification when needed
function set_value(new_value)
    local old_value = state.value

    state.value = new_value

    if old_value ~= new_value then
        ui.notify(
            string.format(_T("Value changed: %d → %d"), old_value, new_value),
            "Info"
        )
    end
end

-- Batch operation completion notification
function process_batch(items)
    local success_count = 0
    local error_count = 0

    for i, item in ipairs(items) do
        if process_item(item) then
            success_count = success_count + 1
        else
            error_count = error_count + 1
        end
    end

    -- Display single summary notification
    if error_count == 0 then
        ui.notify(
            string.format(_T("Processed %d items successfully"), success_count),
            "Success"
        )
    else
        ui.notify(
            string.format(_T("Processed %d items, %d failed"), success_count, error_count),
            "Warning"
        )
    end
end
```

##### Custom Icons (Settings → Icons → Notification)

```lua
-- Notification icons are set in configuration:
config.data.notification_icons = {
    Success = "success.png",
    Warning = "warning.png",
    Error = "error.png",
    Info = "info.png",
    Join = "player_join.png",
    Leave = "player_leave.png",
    -- ... more custom icons
}
```

#### Notification System Configuration

The notification system supports rich customization options (Settings → Notify):

```lua
-- Position settings
config.data.notification_position = "bottom_right"
-- Options: "bottom_right", "bottom_left", "top_right", "top_left"

-- Style settings
config.data.base.notif_width = 320           -- Width
config.data.base.notif_min_height = 60       -- Minimum height
config.data.base.notif_radius = 8            -- Border radius
config.data.base.notif_title_size = 17       -- Title font size
config.data.base.notif_message_size = 14     -- Message font size
config.data.base.notif_icon_size = 32        -- Icon size

-- Animation settings
config.data.base.notif_duration = 5.0        -- Display duration (seconds)
config.data.base.notif_anim_speed_in = 4.0   -- Entrance animation speed
config.data.base.notif_anim_stack_speed = 15.0  -- Stack animation speed
config.data.base.notif_exit_speed_multiplier = 1.5  -- Exit speed multiplier

-- Time bar settings
config.data.base.notif_time_bar_enabled = true   -- Enable time bar
config.data.base.notif_time_bar_height = 2       -- Time bar height
config.data.base.notif_time_bar_opacity = 180    -- Time bar opacity

-- Spacing settings
config.data.base.notif_spacing = 8           -- Spacing between notifications
config.data.base.notif_margin_x = 20         -- Screen margin X
config.data.base.notif_margin_y = 20         -- Screen margin Y
config.data.base.notif_max_count = 12        -- Maximum simultaneous display count
```

#### Best Practices

**✅ Recommended Practices**

```lua
-- 1. Use meaningful notification types
ui.notify(_T("Settings saved"), "Success")     -- ✅ Clear and specific
ui.notify(_T("Invalid input"), "Error")        -- ✅ Appropriate type

-- 2. Keep notification messages concise
ui.notify(_T("God mode enabled"), "Info")      -- ✅ Short and direct

-- 3. Display notifications only for important operations
function save_config()
    -- Important operation, display notification
    config.save()
    ui.notify(_T("Configuration saved"), "Success")
end

-- 4. Use translation for multi-language support
ui.notify(_T("Action completed"), "Success")   -- ✅ Multi-language support

-- 5. Display summary for batch operations
-- Don't show notification for each item, display final result
process_all_items()
ui.notify(_T("All items processed"), "Success")
```

**❌ Common Mistakes**

```lua
-- 1. Inappropriate notification type
ui.notify(_T("Error occurred"), "Success")     -- ❌ Wrong type

-- 2. Too frequent notifications
function module.on_tick()
    if state.enabled then
        ui.notify(_T("Processing..."), "Info")  -- ❌ Display every frame!
    end
end

-- 3. Notification message too long
ui.notify(_T("The configuration file has been successfully saved to the disk and all settings have been applied to the current session"), "Success")  -- ❌ Too long!

-- 4. Not using translation
ui.notify("Success", "Success")                 -- ❌ No multi-language support

-- 5. Displaying notification for each item
for i, item in ipairs(items) do
    process_item(item)
    ui.notify("Item " .. i .. " done", "Info")  -- ❌ Spam!
end
```

#### Performance Considerations

```lua
-- ✅ Recommended: Limit notification frequency
local last_notify_time = 0
local NOTIFY_COOLDOWN = 1.0  -- 1 second cooldown

function show_status_update()
    local now = os.clock()

    if now - last_notify_time >= NOTIFY_COOLDOWN then
        ui.notify(_T("Status updated"), "Info")
        last_notify_time = now
    end
end

-- ✅ Recommended: Merge duplicate notifications
local pending_notifications = {}

function queue_notification(message, type)
    pending_notifications[message] = type
end

function flush_notifications()
    for message, type in pairs(pending_notifications) do
        ui.notify(message, type)
    end
    pending_notifications = {}
end

-- Flush at appropriate time
function module.on_menu_close()
    flush_notifications()
end
```

#### Debugging Notification System

```lua
-- Test all notification types (Settings → Notify → Test Notifications)
function test_all_notifications()
    ui.notify(_T("Success notification"), "Success")
    ui.notify(_T("Warning notification"), "Warning")
    ui.notify(_T("Error notification"), "Error")
    ui.notify(_T("Info notification"), "Info")
    ui.notify(_T("Player joined"), "Join")
    ui.notify(_T("Player left"), "Leave")
    ui.notify(_T("Spectator detected"), "Spectator")
    ui.notify(_T("Modder detected"), "Modder Detection")
end

-- View notification configuration
if debugger then
    debugger.info("[Notification] Config", {
        position = config.data.notification_position,
        width = config.data.base.notif_width,
        duration = config.data.base.notif_duration,
        max_count = config.data.base.notif_max_count
    })
end
```

---

## Development Guide

### Adding a New Feature Module

#### 1. Create Module File

Create a new file `MyFeature.lua` in the `Features/` directory:

```lua
-- -------------------------------------------------------------------------
-- Features/MyFeature.lua
-- Feature Description
-- -------------------------------------------------------------------------

local module = {}

-- [Dependencies]
local ui = nil
local config = nil
local natives = nil
local lang = nil
local debugger = nil
local features_config = nil

-- [Lua 5.4] <const>
local MODULE_NAME <const> = "MyFeature"

-- [State]
local state = {
    enabled = false,
    value = 0
}

-- [Translation Helper]
local function _T(text)
    if lang then return lang.get(text) end
    return text
end

-- [Config Helpers]
local function load_config()
    if not features_config then return end
    local saved = features_config.get_module(MODULE_NAME)
    if not saved then return end

    state.enabled = saved.enabled or false
    state.value = saved.value or 0
end

local function save_config()
    if not features_config then return end
    features_config.set_module(MODULE_NAME, {
        enabled = state.enabled,
        value = state.value
    }, true)
end

-- [Module Interface]
function module.init(ui_lib, cfg_lib, nat_lib, lang_lib, _, _, debug_lib)
    ui = ui_lib
    config = cfg_lib
    natives = nat_lib
    lang = lang_lib
    debugger = debug_lib

    -- Load features config
    local ok, fc = pcall(require, "ClickUl.Config.features")
    if ok and fc then features_config = fc end

    if debugger then
        debugger.info("[MyFeature] Module initialized")
    end

    load_config()
    return true
end

function module.draw()
    ui.Group(_T("My Feature"), function()
        state.enabled = ui.Checkbox(_T("Enable"), state.enabled)

        if state.enabled then
            state.value = ui.Slider(_T("Value"), state.value, 0, 100, 1)

            if ui.Button(_T("Apply")) then
                -- Execute operation
                if ui.notify then
                    ui.notify(_T("Applied successfully"), "Success")
                end
            end
        end
    end)
end

function module.on_tick()
    if not state.enabled then return end
    if not natives then return end

    -- Per-frame logic execution
end

function module.on_menu_close()
    save_config()
end

return module
```

#### 2. Register Module to Main Menu

Add in `main.lua`:

```lua
local my_feature = require("ClickUl.Features.MyFeature")

-- Initialize
my_feature.init(UI, config, natives, lang, json, lexis, debugger)

-- Add tab
menu.add_tab("My Feature", function()
    my_feature.draw()
end)

-- Add to on_tick
function on_tick()
    my_feature.on_tick()
end
```

### Adding Translations

#### 1. Mark Text for Translation in English Base File

Wrap all user-visible text with `_T()`:

```lua
ui.Button(_T("Click Me"))
ui.Checkbox(_T("Enable Feature"), state.enabled)
ui.Group(_T("Settings"), function() ... end)
```

#### 2. Create Translation File

Create a language file in the `languages/` directory (e.g., `zh-cn.json`):

```json
{
  "Click Me": "点击我",
  "Enable Feature": "启用功能",
  "Settings": "设置",
  "Applied successfully": "应用成功"
}
```

#### 3. Test Translations

1. Switch language in-game: Settings → General → Language → Select "zh-cn"
2. Close and reopen menu to verify translation is working

### Using Natives

#### 1. Cache Native Functions

```lua
-- [Natives Cache]
local N = {}

function module.init(ui_lib, cfg_lib, nat_lib, ...)
    natives = nat_lib

    if natives then
        local function get(name) return natives[name] end

        N.PLAYER_ID = get("PLAYER_ID")
        N.PLAYER_PED_ID = get("PLAYER_PED_ID")
        N.IS_PED_IN_ANY_VEHICLE = get("IS_PED_IN_ANY_VEHICLE")
        N.GET_VEHICLE_PED_IS_IN = get("GET_VEHICLE_PED_IS_IN")
    end
end
```

#### 2. Use Native Functions

```lua
function module.on_tick()
    if not natives then return end

    local player_id = N.PLAYER_ID and N.PLAYER_ID() or 0
    local ped = N.PLAYER_PED_ID and N.PLAYER_PED_ID() or 0

    if N.IS_PED_IN_ANY_VEHICLE and N.IS_PED_IN_ANY_VEHICLE(ped, false) then
        local vehicle = N.GET_VEHICLE_PED_IS_IN(ped, false)
        -- Handle vehicle logic
    end
end
```

### Adding Debug Logs

```lua
function module.init(...)
    if debugger then
        debugger.info("[MyFeature] Initializing module")
        debugger.debug("[MyFeature] Debug info", {key = "value"})
    end
end

function module.on_some_action()
    if debugger then
        debugger.warn("[MyFeature] Warning message")
        debugger.error("[MyFeature] Error occurred", {error = err})
    end
end
```

---

## Code Standards

### Lua 5.4 Best Practices

#### Using `<const>` for Constants

```lua
-- ✅ Recommended
local MAX_VALUE <const> = 100
local MODULE_NAME <const> = "MyModule"
local table_insert <const> = table.insert
local math_floor <const> = math.floor

-- ❌ Not recommended
local MAX_VALUE = 100  -- May be accidentally modified
```

#### Using `<close>` for Automatic Resource Cleanup

```lua
local f <close> = file.open(path)
-- Automatically calls f:close() when function ends
```

#### Localizing Global Functions

```lua
-- ✅ Recommended: Reduce global lookups
local type <const> = type
local pairs <const> = pairs
local ipairs <const> = ipairs
local pcall <const> = pcall

-- ❌ Not recommended: Global lookup on every call
for i, v in ipairs(table) do ... end
```

### Naming Conventions

```lua
-- Module name: PascalCase
Features/PlayerInfo.lua

-- Function name: snake_case
function module.on_tick() end
local function calculate_damage() end

-- Variable name: snake_case
local player_id = 0
local is_enabled = false

-- Constant name: UPPER_SNAKE_CASE
local MAX_PLAYERS <const> = 32
local DEFAULT_VALUE <const> = 100

-- Private function: Underscore prefix
local function _internal_helper() end

-- Table keys: Use quotes (if containing spaces)
colors["Window Background"] = {r=20, g=20, b=25, a=240}
```

### Error Handling

```lua
-- ✅ Recommended: Wrap potentially failing operations with pcall
local ok, result = pcall(natives.SOME_NATIVE, arg1, arg2)
if ok and result then
    -- Success
else
    if debugger then
        debugger.warn("Native call failed", {error = result})
    end
end

-- ✅ Recommended: Early return
function module.on_tick()
    if not enabled then return end
    if not natives then return end
    -- Main logic
end

-- ❌ Not recommended: Deep nesting
function module.on_tick()
    if enabled then
        if natives then
            if condition then
                -- Main logic
            end
        end
    end
end
```

### Performance Optimization Suggestions

#### 1. Cache Frequently Accessed Values

```lua
-- ✅ Recommended
local config_base = config.data.base
local w = config_base.w
local h = config_base.h
local padding = config_base.padding

-- ❌ Not recommended
renderer.draw_rect(x, y, config.data.base.w, config.data.base.h, ...)
```

#### 2. Avoid Creating Closures in Loops

```lua
-- ✅ Recommended
local function draw_item(item, index)
    -- Handle item
end

for i, item in ipairs(items) do
    draw_item(item, i)
end

-- ❌ Not recommended
for i, item in ipairs(items) do
    ui.Button("Item " .. i, function()
        -- Creates new closure on every iteration
    end)
end
```

#### 3. Use Caching to Avoid Repeated Calculations

```lua
-- ✅ Recommended
local text_cache = {}

local function get_text_size(text)
    if text_cache[text] then
        return text_cache[text]
    end

    local size = expensive_calculation(text)
    text_cache[text] = size
    return size
end

-- ❌ Not recommended
local function get_text_size(text)
    return expensive_calculation(text)  -- Recalculate every time
end
```

#### 4. Limit Per-Frame Operation Frequency

```lua
-- ✅ Recommended
local last_update = 0
local UPDATE_INTERVAL = 1.0  -- 1 second

function module.on_tick()
    local now = os.clock()
    if now - last_update < UPDATE_INTERVAL then return end
    last_update = now

    -- Execute expensive operation
end

-- ❌ Not recommended
function module.on_tick()
    -- Execute expensive operation every frame
    expensive_operation()
end
```

---

## Debugging Methods

### Using Built-in Debug System

#### 1. Enable Debug System

In-game: Settings → Debug → Enable Debug System

#### 2. Set Log Level

```lua
-- Log levels:
-- 1 = DEBUG (all information)
-- 2 = INFO  (general information)
-- 3 = WARN  (warnings)
-- 4 = ERROR (errors only)
```

Use in code:

```lua
if debugger then
    debugger.debug("[MyModule] Debug info", {var = value})
    debugger.info("[MyModule] Info message")
    debugger.warn("[MyModule] Warning message")
    debugger.error("[MyModule] Error message", {error = err})
end
```

#### 3. View Debug Logs

- **Real-time viewing**: View through debug UI in-game
- **Export to file**: Settings → Debug → Dump History to File
- **Log location**: `ClickUl/logs/`

### Common Debugging Techniques

#### 1. Check Module Initialization

```lua
function module.init(...)
    if debugger then
        debugger.info("[MyModule] Initializing with dependencies", {
            ui = ui ~= nil,
            config = config ~= nil,
            natives = natives ~= nil
        })
    end
end
```

#### 2. Monitor State Changes

```lua
function module.draw()
    local old_enabled = state.enabled
    state.enabled = ui.Checkbox("Enable", state.enabled)

    if state.enabled ~= old_enabled then
        if debugger then
            debugger.info("[MyModule] State changed", {
                enabled = state.enabled
            })
        end
    end
end
```

#### 3. Catch Errors

```lua
function module.on_tick()
    local ok, err = pcall(function()
        -- Code that might fail
        risky_operation()
    end)

    if not ok then
        if debugger then
            debugger.error("[MyModule] Operation failed", {
                error = tostring(err)
            })
        end
    end
end
```

#### 4. Performance Analysis

```lua
function module.on_expensive_operation()
    local start_time = os.clock()

    -- Execute operation
    perform_operation()

    local elapsed = os.clock() - start_time

    if debugger and elapsed > 0.016 then  -- > 16ms (1 frame)
        debugger.warn("[MyModule] Slow operation", {
            elapsed_ms = elapsed * 1000
        })
    end
end
```

### Debugging Native Calls

```lua
function call_native_safely(native_func, ...)
    if not native_func then
        if debugger then
            debugger.error("[MyModule] Native function not found")
        end
        return nil
    end

    local ok, result = pcall(native_func, ...)

    if not ok then
        if debugger then
            debugger.error("[MyModule] Native call failed", {
                error = tostring(result)
            })
        end
        return nil
    end

    return result
end

-- Usage
local player_id = call_native_safely(N.PLAYER_ID)
```

---

## Performance Optimization

### Rendering Optimization

#### 1. Text Object Caching

Renderer has implemented automatic caching, but note:

```lua
-- ✅ Recommended: Static text will be cached
renderer.draw_text_ex("Static Text", x, y, size, color)

-- ❌ Not recommended: Dynamic text won't be cached (contains numbers)
renderer.draw_text_ex("Value: " .. value, x, y, size, color)

-- ✅ Solution: Use cache ID
local cache_id = "value_text_" .. math.floor(value / 10)  -- Quantize value
renderer.draw_text_ex("Value: " .. value, x, y, size, color, 0, 0, cache_id)
```

#### 2. Color Object Reuse

```lua
-- ✅ Recommended: Cache color objects
local colors = {
    white = color(255, 255, 255, 255),
    red = color(255, 0, 0, 255),
    transparent = color(0, 0, 0, 0)
}

renderer.draw_rect(x, y, w, h, colors.white)

-- ❌ Not recommended: Create new object each time
renderer.draw_rect(x, y, w, h, color(255, 255, 255, 255))
```

#### 3. Reduce Draw Calls

```lua
-- ✅ Recommended: Batch drawing
local items = get_items()
for i, item in ipairs(items) do
    draw_item(item)  -- Single draw call
end

-- ❌ Not recommended: Multiple queries + multiple draws
for i = 1, get_item_count() do
    local item = get_item(i)
    draw_item(item)
end
```

### Memory Optimization

#### 1. Limit Cache Size

Renderer has implemented cache limits:

```lua
local VEC2_CACHE_LIMIT = 4096
local TEXT_CACHE_LIMIT = 1500
local CLEAN_LABEL_MAX = 2048
```

When the limit is exceeded, the cache is cleared and rebuilt.

#### 2. Release Resources Promptly

```lua
-- Clean up large data no longer in use
function module.on_menu_close()
    large_data_cache = nil
    collectgarbage("collect")  -- Optional: Force GC
end
```

#### 3. Avoid Memory Leaks

```lua
-- ❌ Not recommended: Event listener leak
function register_callback()
    local callback = function() ... end
    event_system.add_listener("event", callback)
    -- callback cannot be GC'd because event_system holds reference
end

-- ✅ Recommended: Provide cleanup method
local callback_id = nil

function register_callback()
    callback_id = event_system.add_listener("event", function() ... end)
end

function module.on_unload()
    if callback_id then
        event_system.remove_listener(callback_id)
    end
end
```

### Logic Optimization

#### 1. Early Return

```lua
-- ✅ Recommended
function module.on_tick()
    if not enabled then return end
    if not player_valid() then return end
    -- Main logic
end

-- ❌ Not recommended
function module.on_tick()
    if enabled and player_valid() then
        -- Main logic
    end
end
```

#### 2. Batch Operations

```lua
-- ✅ Recommended
local function apply_changes_batch(changes)
    for i, change in ipairs(changes) do
        apply_change(change)
    end
end

apply_changes_batch(pending_changes)
pending_changes = {}

-- ❌ Not recommended
for i, change in ipairs(pending_changes) do
    apply_change(change)
    table.remove(pending_changes, i)  -- O(n) operation
end
```

#### 3. Lazy Evaluation

```lua
-- ✅ Recommended: Lazy loading
local cached_value = nil

local function get_expensive_value()
    if cached_value == nil then
        cached_value = expensive_calculation()
    end
    return cached_value
end

-- ❌ Not recommended: Immediate calculation
local expensive_value = expensive_calculation()  -- Calculates even if not used
```

---

## FAQ

### Q1: How to add a new UI control?

**A**: Add a new function in `UI/widgets.lua`:

```lua
function M.MyControl(label, value, ...)
    local state_key = get_state_key(label)

    -- 1. Calculate position and size
    local x, y = layout.get_current_pos()
    local w, h = layout.get_item_size()

    -- 2. Handle input
    local is_hover = input.is_hovering(x, y, w, h)
    local is_clicked = is_hover and input.is_clicking()

    -- 3. Draw
    renderer.draw_rect(x, y, w, h, color_obj)
    renderer.draw_text_ex(label, x + 10, y + h/2, size, text_color)

    -- 4. Update layout
    layout.advance(h)

    -- 5. Return new value
    return new_value
end
```

### Q2: How to handle cross-module communication?

**A**: Use global event system or shared state:

```lua
-- Method 1: Global state
_G.shared_state = _G.shared_state or {}
_G.shared_state.my_data = value

-- Method 2: Event publish/subscribe
local event_bus = require("event_bus")
event_bus.publish("player_changed", player_data)
event_bus.subscribe("player_changed", function(data) ... end)
```

### Q3: How to implement configuration migration?

**A**: Check and migrate old configuration during module init:

```lua
function module.init(...)
    local saved = features_config.get_module(MODULE_NAME)

    -- Migrate old fields
    if saved and saved.old_field ~= nil then
        saved.new_field = convert_old_to_new(saved.old_field)
        saved.old_field = nil
        features_config.set_module(MODULE_NAME, saved, true)
    end
end
```

### Q4: What to do if Native calls fail?

**A**:
1. Confirm Native name is correct (refer to LEXIS API documentation)
2. Wrap call with pcall
3. Check parameter types and order
4. Add debug logs

```lua
local ok, result = pcall(natives.SOME_NATIVE, arg1, arg2)
if not ok then
    if debugger then
        debugger.error("Native failed", {
            native = "SOME_NATIVE",
            args = {arg1, arg2},
            error = result
        })
    end
end
```

### Q5: How to optimize large list rendering?

**A**: Implement virtual scrolling (render only visible items):

```lua
function draw_large_list(items)
    local scroll_y = layout.get_scroll()
    local visible_height = layout.get_visible_height()
    local item_height = 40

    -- Calculate visible range
    local start_idx = math.floor(scroll_y / item_height) + 1
    local end_idx = math.ceil((scroll_y + visible_height) / item_height)

    -- Only render visible items
    for i = start_idx, math.min(end_idx, #items) do
        draw_item(items[i])
    end

    -- Set total content height (for scrollbar)
    layout.set_content_height(#items * item_height)
end
```

### Q6: What to do if text displays as garbled characters?

**A**:
1. Ensure JSON files are saved with UTF-8 encoding
2. Check translation file format is correct
3. Confirm font supports the character set displayed
4. Use an online JSON validator to check syntax

### Q7: How to implement theme hot-reloading?

**A**: Theme system already supports runtime switching:

```lua
-- In Setting.lua
if new_theme_idx ~= current_theme_idx then
    theme.load_theme(theme_name)

    -- Refresh all caches
    ui.force_update_scale()
    ui.update_colors()
    ui.refresh_background()
    ui.clear_cache()
end
```

### Q8: How to add hotkeys?

**A**: Listen for keyboard input in `UI/input.lua`:

```lua
-- In main.lua or module
if input.is_key_just_pressed(KEY_F5) then
    toggle_menu()
end

if input.is_key_down(KEY_LSHIFT) and input.is_key_just_pressed(KEY_R) then
    reload_config()
end
```


---

## Contributing Guidelines

### Code Submission Checklist

- [ ] Code complies with Lua 5.4 standards
- [ ] Use `<const>` to mark constants
- [ ] Localize global function references
- [ ] All user-visible text wrapped with `_T()`
- [ ] Add necessary debug logs
- [ ] Wrap potentially failing operations with pcall
- [ ] Call save_config() when configuration changes
- [ ] Module implements init, draw, on_tick, on_menu_close interfaces
- [ ] Add necessary comments and documentation

### Code Review Points

1. **Performance**: Are there unnecessary calculations or memory allocations?
2. **Error Handling**: Are edge cases and errors handled correctly?
3. **Readability**: Is the code clear and easy to understand?
4. **Maintainability**: Is it easy to modify and extend?
5. **Internationalization**: Does it support multiple languages?


---

**Happy Coding! 🚀**
